/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.modules.tasklist.ui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JToolBar;
import java.io.Serializable;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.modules.tasklist.filter.FilterRepository;
import org.netbeans.modules.tasklist.impl.ScannerList;
import org.netbeans.modules.tasklist.impl.ScanningScopeList;
import org.netbeans.modules.tasklist.impl.TaskList;
import org.netbeans.modules.tasklist.impl.TaskManagerImpl;
import org.netbeans.spi.tasklist.Task;
import org.netbeans.spi.tasklist.TaskScanningScope;
import org.openide.util.Cancellable;
import org.openide.util.HelpCtx;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;

/**
 * Top component which displays something.
 */
final class TaskListTopComponent extends TopComponent {
    
    private static TaskListTopComponent instance;
    /** path to the icon used by the component and its open action */
    static final String ICON_PATH = "org/netbeans/modules/tasklist/ui/resources/taskList.png"; //NOI18N
    
    private static final String PREFERRED_ID = "TaskListTopComponent"; //NOI18N
    
    private TaskManagerImpl taskManager;
    private PropertyChangeListener scopeListListener;
    private PropertyChangeListener scannerListListener;
    private TaskListModel model;
    private FilterRepository filters;
    private TaskListTable table;
    private PropertyChangeListener changeListener;
    private TaskList.Listener tasksListener;

    private static final boolean isMacLaf = "Aqua".equals(UIManager.getLookAndFeel().getID());
    private static final Color macBackground = UIManager.getColor("NbExplorerView.background");
    
    private TaskListTopComponent() {
        taskManager = TaskManagerImpl.getInstance();
        
        initComponents();
//        statusBarPanel.setMinimumSize(new JLabel("dummy").getPreferredSize());
        setName(NbBundle.getMessage(TaskListTopComponent.class, "CTL_TaskListTopComponent")); //NOI18N
        setToolTipText(NbBundle.getMessage(TaskListTopComponent.class, "HINT_TaskListTopComponent")); //NOI18N
        setIcon(ImageUtilities.loadImage(ICON_PATH, true));
        
        tableScroll.addMouseListener( new MouseAdapter() {
            @Override
            public void mousePressed( MouseEvent e ) {
                maybePopup( e );
            }
            
            @Override
            public void mouseReleased( MouseEvent e ) {
                maybePopup( e );
            }
        });
        if( isMacLaf ) {
            tableScroll.setBackground(macBackground);
            tableScroll.getViewport().setBackground(macBackground);
            toolbar.setBackground(macBackground);
            statusBarPanel.setBackground(macBackground);
        }

    }
    
    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        toolbar = new javax.swing.JToolBar();
        tableHolderPanel = new javax.swing.JPanel();
        tableScroll = new javax.swing.JScrollPane();
        toolbarSeparator = new javax.swing.JSeparator();
        statusSeparator = new javax.swing.JSeparator();
        statusBarPanel = new javax.swing.JPanel();

        setLayout(new java.awt.GridBagLayout());

        toolbar.setFloatable(false);
        toolbar.setOrientation(javax.swing.SwingConstants.VERTICAL);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridheight = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weighty = 1.0;
        add(toolbar, gridBagConstraints);

        tableHolderPanel.setOpaque(false);

        tableScroll.setBorder(null);

        javax.swing.GroupLayout tableHolderPanelLayout = new javax.swing.GroupLayout(tableHolderPanel);
        tableHolderPanel.setLayout(tableHolderPanelLayout);
        tableHolderPanelLayout.setHorizontalGroup(
            tableHolderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(tableScroll, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 397, Short.MAX_VALUE)
        );
        tableHolderPanelLayout.setVerticalGroup(
            tableHolderPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(tableScroll, javax.swing.GroupLayout.DEFAULT_SIZE, 299, Short.MAX_VALUE)
        );

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        add(tableHolderPanel, gridBagConstraints);

        toolbarSeparator.setOrientation(javax.swing.SwingConstants.VERTICAL);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridheight = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weighty = 1.0;
        add(toolbarSeparator, gridBagConstraints);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
        gridBagConstraints.weightx = 1.0;
        add(statusSeparator, gridBagConstraints);

        statusBarPanel.setLayout(new java.awt.BorderLayout());
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHEAST;
        gridBagConstraints.weightx = 1.0;
        add(statusBarPanel, gridBagConstraints);
    }// </editor-fold>//GEN-END:initComponents
    
    
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JPanel statusBarPanel;
    private javax.swing.JSeparator statusSeparator;
    private javax.swing.JPanel tableHolderPanel;
    private javax.swing.JScrollPane tableScroll;
    private javax.swing.JToolBar toolbar;
    private javax.swing.JSeparator toolbarSeparator;
    // End of variables declaration//GEN-END:variables
    
    /**
     * Gets default instance. Do not use directly: reserved for *.settings files only,
     * i.e. deserialization routines; otherwise you could get a non-deserialized instance.
     * To obtain the singleton instance, use {@link findInstance}.
     */
    public static synchronized TaskListTopComponent getDefault() {
        if (instance == null) {
            instance = new TaskListTopComponent();
        }
        return instance;
    }
    
    /**
     * Obtain the TaskListTopComponent instance. Never call {@link #getDefault} directly!
     */
    public static synchronized TaskListTopComponent findInstance() {
        TopComponent win = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
        if (win == null) {
            getLogger().log( Level.INFO, "Cannot find TaskList component. It will not be located properly in the window system." ); //NOI18N
            return getDefault();
        }
        if (win instanceof TaskListTopComponent) {
            return (TaskListTopComponent)win;
        }
        getLogger().log( Level.INFO,
                "There seem to be multiple components with the '" + PREFERRED_ID + //NOI18N
                "' ID. That is a potential source of errors and unexpected behavior."); //NOI18N
        return getDefault();
    }
    
    @Override
    public int getPersistenceType() {
        return TopComponent.PERSISTENCE_ALWAYS;
    }
    
    @Override
    public void componentOpened() {
        if( null == model ) {
            toolbarSeparator.setVisible(false);
            statusSeparator.setVisible(false);
            tableScroll.setViewportView( createNoTasksMessage() );
        }

        WindowManager.getDefault().invokeWhenUIReady(new Runnable() {
            @Override
            public void run() {
                TaskManagerImpl.RP.post(new Runnable() {
                    @Override
                    public void run() {
                        init();
                    }
                });
            }
        });
    }
    
    private void init() {
        TaskScanningScope activeScope = getActiveScope();
        if( null == filters ) {
            try {
                filters = FilterRepository.getDefault();
                filters.load();
            } catch( IOException ioE ) {
                getLogger().log( Level.INFO, ioE.getMessage(), ioE );
            }
        }

        if( null == changeListener ) {
            changeListener = createChangeListener();
            taskManager.addPropertyChangeListener( TaskManagerImpl.PROP_WORKING_STATUS, changeListener );
        }
        
        if( null == model ) {
            table = new TaskListTable();
            if( isMacLaf )
                table.setBackground(macBackground);
            //later on the button in the toolbar will switch to the previously used table model
            model = new TaskListModel( taskManager.getTasks() );
            table.setModel( model );
            tasksListener = new TaskList.Listener() {
                @Override
                public void tasksAdded( List<? extends Task> tasks ) {
                    Runnable runnable = new Runnable() {
                        @Override
                        public void run() {
                            tableScroll.setViewportView( table );
                            Color background = UIManager.getColor("TextArea.background"); //NOI18N
                            if( null != background )
                                tableScroll.getViewport().setBackground( background );
                            tableScroll.setBorder( BorderFactory.createEmptyBorder() );
                            if( isMacLaf ) {
                                table.setBackground(macBackground);
                                tableScroll.getViewport().setBackground(macBackground);
                            }
                        }
                    };
                    taskManager.getTasks().removeListener(tasksListener);
                    if( SwingUtilities.isEventDispatchThread() ) {
                        runnable.run();
                    } else {
                        SwingUtilities.invokeLater(runnable);
                    }
                    tasksListener = null;
                }

                @Override
                public void tasksRemoved(List<? extends Task> tasks) {
                }

                @Override
                public void cleared() {
                }
            };
            taskManager.getTasks().addListener(tasksListener);
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    tableScroll.setViewportView( createNoTasksMessage() );
                    tableScroll.setBorder( BorderFactory.createEmptyBorder() );
                    
                    statusBarPanel.add( new CountStatusBar(taskManager.getTasks()), BorderLayout.WEST );
                    statusBarPanel.add( new ScopeStatusBar(taskManager), BorderLayout.CENTER );

                    toolbarSeparator.setVisible(true);
                    statusSeparator.setVisible(true);
                    rebuildToolbar();
                }
            });
        }
        SwingUtilities.invokeLater( new Runnable() {
            @Override
            public void run() {
                ScanningScopeList.getDefault().addPropertyChangeListener( getScopeListListener() );
                ScannerList.getFileScannerList().addPropertyChangeListener( getScannerListListener() );
                ScannerList.getPushScannerList().addPropertyChangeListener( getScannerListListener() );
            }
        });
        
        final TaskScanningScope scopeToObserve = activeScope;
        taskManager.observe( scopeToObserve, filters.getActive() );
    }

    private Component createNoTasksMessage() {
        JPanel panel = new JPanel( new BorderLayout() );
        if( isMacLaf ) {
            panel.setBackground(macBackground);
        } else {
            Color background = UIManager.getColor("TextArea.background"); //NOI18N
            if( null != background )
                panel.setBackground( background );
        }
        JLabel msg = new JLabel( NbBundle.getMessage(TaskListTopComponent.class, "LBL_NoTasks" ) ); //NOI18N
        msg.setHorizontalAlignment(JLabel.CENTER);
        msg.setEnabled(false);
        msg.setOpaque(false);
        panel.add( msg, BorderLayout.CENTER );
        return panel;
    }
    
    @Override
    public void componentClosed() {
        ScanningScopeList.getDefault().removePropertyChangeListener( getScopeListListener() );
        ScannerList.getFileScannerList().removePropertyChangeListener( getScannerListListener() );
        ScannerList.getPushScannerList().removePropertyChangeListener( getScannerListListener() );
        taskManager.observe( null, null );
        if( null != changeListener ) {
            taskManager.removePropertyChangeListener( TaskManagerImpl.PROP_WORKING_STATUS, changeListener );
            changeListener = null;
        }
        synchronized( this ) {
            if( null != progress )
                progress.finish();
            progress = null;
        }
        try {
            FilterRepository.getDefault().save();
        } catch( IOException ioE ) {
            getLogger().log( Level.INFO, null, ioE );
        }
    }
    
    @Override
    public void requestActive() {
        super.requestActive();
        if( null != table )
            table.requestFocusInWindow();
    }
    
    @Override
    public Object writeReplace() {
        return new ResolvableHelper();
    }
    
    @Override
    protected String preferredID() {
        return PREFERRED_ID;
    }
    
    private void maybePopup( MouseEvent e ) {
        if( e.isPopupTrigger() && null != table ) {
            e.consume();
            JPopupMenu popup = Util.createPopup( table );
            popup.show( this, e.getX(), e.getY() );
        }
    }
    
    private PropertyChangeListener getScopeListListener() {
        if( null == scopeListListener ) {
            scopeListListener = new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent arg0) {
                    ScanningScopeList scopeList = ScanningScopeList.getDefault();
                    List<TaskScanningScope> newScopes = scopeList.getTaskScanningScopes();
                    if( newScopes.isEmpty() ) {
                        getLogger().log( Level.INFO, "No task scanning scope found" ); //NOI18N
                    }
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            rebuildToolbar();
                        }
                    });
                }
            };
        }
        return scopeListListener;
    }
    
    private PropertyChangeListener getScannerListListener() {
        if( null == scannerListListener ) {
            scannerListListener = new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent e) {
                    final TaskScanningScope scopeToObserve = taskManager.getScope();
                    taskManager.observe( null, null );
                    SwingUtilities.invokeLater( new Runnable() {
                        @Override
                        public void run() {
                            taskManager.observe( scopeToObserve, filters.getActive() );
                        }
                    });
                }
            };
        }
        return scannerListListener;
    }
    
    private void rebuildToolbar() {
        toolbar.removeAll();
        toolbar.setFocusable( false );
        //scope buttons
        List<TaskScanningScope> scopes = ScanningScopeList.getDefault().getTaskScanningScopes();
        for( TaskScanningScope scope : scopes ) {
            final ScopeButton scopeButton = new ScopeButton( taskManager, scope );
            scopeButton.setSelected(scope.equals(Settings.getDefault().getActiveScanningScope()));
            toolbar.add(scopeButton);
        }
        toolbar.add( new JToolBar.Separator() );
        //filter
        JToggleButton toggleFilter = new FiltersMenuButton( filters.getActive() );
        toolbar.add( toggleFilter );
        //grouping & other butons
        toolbar.addSeparator();
        final JToggleButton toggleGroups = new JToggleButton( ImageUtilities.loadImageIcon("org/netbeans/modules/tasklist/ui/resources/groups.png", false)); //NOI18N
        toggleGroups.addItemListener( new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                switchTableModel( e.getStateChange() == ItemEvent.SELECTED );
                Settings.getDefault().setGroupTasksByCategory( toggleGroups.isSelected() );
                toggleGroups.setToolTipText( toggleGroups.isSelected() 
                        ? NbBundle.getMessage( TaskListTopComponent.class, "HINT_TasksAsList" )  //NOI18N
                        : NbBundle.getMessage( TaskListTopComponent.class, "HINT_GrouppedTasks" ) ); //NOI18N
            }
        });
        toggleGroups.setSelected( Settings.getDefault().getGroupTasksByCategory() );
        toggleGroups.setToolTipText( toggleGroups.isSelected() 
                        ? NbBundle.getMessage( TaskListTopComponent.class, "HINT_TasksAsList" )  //NOI18N
                        : NbBundle.getMessage( TaskListTopComponent.class, "HINT_GrouppedTasks" ) ); //NOI18N
        toggleGroups.setFocusable( false );
        toolbar.add( toggleGroups );
    }
    
    private void switchTableModel( boolean useFoldingModel ) {
        if( useFoldingModel ) {
            model = new FoldingTaskListModel( taskManager.getTasks() );
            table.setModel( model );
            statusBarPanel.removeAll();
        } else {
            model = new TaskListModel( taskManager.getTasks() );
            table.setModel( model );
            statusBarPanel.add( new CountStatusBar(taskManager.getTasks()), BorderLayout.WEST );
            statusBarPanel.add( new ScopeStatusBar(taskManager), BorderLayout.CENTER );
        }
        statusBarPanel.setVisible( !useFoldingModel );
        statusSeparator.setVisible( !useFoldingModel );
    }
    
    private static Logger getLogger() {
        return Logger.getLogger( TaskListTopComponent.class.getName() );
    }

    private ProgressHandle progress;
    private PropertyChangeListener createChangeListener() {
        return new PropertyChangeListener() {
            @Override
            public void propertyChange( PropertyChangeEvent e ) {
                synchronized( TaskListTopComponent.this ) {
                    if( ((Boolean)e.getNewValue()).booleanValue() ) {
                        if( null == progress ) {
                            progress = ProgressHandle.createHandle(
                                    NbBundle.getMessage( TaskListTopComponent.class, "LBL_ScanProgress" ), //NOI18N
                                    new Cancellable() { //NOI18N
                                        @Override
                                        public boolean cancel() {
                                            taskManager.abort();
                                            return true;
                                        }
                                    });
                            progress.start();
                            progress.switchToIndeterminate();
                        }
                    } else {
                        if( null != progress )
                            progress.finish();
                        progress = null;
                    }
                }
            }
        };
    }
    
    static final class ResolvableHelper implements Serializable {
        private static final long serialVersionUID = 1L;
        public Object readResolve() {
            return TaskListTopComponent.getDefault();
        }
    }
    
   /** Defines task list Help ID */
    @Override
    public HelpCtx getHelpCtx () {
        return new HelpCtx(TaskListTopComponent.class);
    }

    private TaskScanningScope getActiveScope() {
        TaskScanningScope activeScope = Settings.getDefault().getActiveScanningScope();
        if( null == activeScope )
            activeScope = ScanningScopeList.getDefault().getDefaultScope();
        return activeScope;
    }
}
